#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h>
#include "client.h"

MyLibraryData_t libraryData;




/* LOG writing function */
void writeToLog(char *src, char *mesaj)
{
    time_t currentTime;
    struct tm *localTime;
    char buffer[256];
    //int fd = open(LOG_FILE, O_CREAT|O_APPEND|O_WRONLY, ALL);
    int fd = STDOUT_FILENO;
    if(fd < 0 )
        return;

    currentTime = time (NULL);
    localTime = localtime (&currentTime);
    /* amprenta de timp*/
    sprintf(buffer, "(%i.%i.%i %i:%i:%i) ",
        localTime->tm_mday,
        localTime->tm_mon,
        localTime->tm_year + 1900,
        localTime->tm_hour,
        localTime->tm_min,
        localTime->tm_sec);
    write(fd, buffer, strlen(buffer));
    /*Sursa mesajului si mesajul pentru inregistrare*/
    write(fd, src, strlen(src));
    write(fd, "-> ", 3);
    write(fd, mesaj, strlen(mesaj));
    write(fd, "\n", 1);

    //close(fd);
}


int getLocalIP()
{
    struct ifreq ifr;
    struct sockaddr_in saddr;
    int fd;
    int localIP = 0;
    fd = socket(AF_INET, SOCK_STREAM, 0);
    strcpy(ifr.ifr_name, "eth0");
    ioctl(fd, SIOCGIFADDR, &ifr);
    saddr = *((struct sockaddr_in *)(&(ifr.ifr_addr))); /* is the address*/
    printf("addr2 = %i, %x \n", saddr.sin_addr.s_addr, saddr.sin_addr.s_addr); /*is the address of the interface in integerformat*/
    localIP = saddr.sin_addr.s_addr;
    close(fd);
    return localIP;
}


//functions implementations
int NAT_initLibrary(int localPort, int clientID, int serverPort, int serverIP)
{
    libraryData.libStatus = LIB_INITIAL_STATUS;
    libraryData.localIP = getLocalIP();
    if (!libraryData.localIP)
    {
        writeToLog("NAT_initLibrary","Unable to find localIP...");
        return -GENERAL_ERROR;
    }
    if (localPort <= 0 || localPort > 65535)
    {
        writeToLog("NAT_initLibrary","The local Port must have a value between 1 and 65535...");
        return -1;
    }
    libraryData.localPort = localPort;

    if (clientID <= 0)
    {
        writeToLog("NAT_initLibrary","The client ID must be positive and greater than zero...");
        return -1;
    }
    libraryData.clientID = clientID;

    if (serverPort <= 0 || serverPort > 65535)
    {
        writeToLog("NAT_initLibrary","The server Port must have a value between 1 and 65535...");
        return -1;
    }
    libraryData.serverPort = serverPort;

    if (!serverIP)
    {
        writeToLog("NAT_initLibrary","The server IP must not be zero...");
        return -1;
    }
    libraryData.serverIP = serverIP;


    libraryData.libStatus = LIB_IS_INITIALIZED;
    
    return 0;
}




//void sendToServer(int localPort, int localIP, int clientID, int cmd, int cmdArgs)
PacketData_t *sendToServer(int cmd, int cmdArgs)
{

	struct sockaddr_in destination;
	struct sockaddr_in localSock;
	int sock, length, n, i;
	char *buf;
	PacketData_t *pdSend, *pdRecv;
	int porneste = 1;

	pdSend = (PacketData_t *) malloc( sizeof( * pdSend) );
	if(!pdSend)
	{
		perror("pdSend = NULL\n");
		return NULL;
	}
	pdSend->myPort = libraryData.localPort;
	pdSend->myIP = libraryData.localIP;
	pdSend->myID = libraryData.clientID;
	pdSend->command = cmd;
	pdSend->cmdArgs = cmdArgs;
	pdSend->responseCode = 0;
	for( i =0; i < 4; i++ )
		pdSend->response[i] = 0;
	printf("Trimit packet: HEXA: %x, %x, %x, %x, %x\n",
		pdSend->myPort, pdSend->myIP, pdSend->myID, pdSend->command, pdSend->cmdArgs);
	printf("VALORIC: %i, %i, %i, %i, %i \n",
		pdSend->myPort, pdSend->myIP, pdSend->myID, pdSend->command, pdSend->cmdArgs);
	
	sock= socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0) perror("Eroare socket trimitere !");

	if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&porneste,sizeof(porneste)) < 0 )
		perror("set sock opt SO_REUSEADDR");


	destination.sin_family = AF_INET;
	//destination.sin_addr.s_addr = htonl(INADDR_BROADCAST);
    destination.sin_addr.s_addr = libraryData.serverIP; //provided in network byte order.
    //TO DO: should be something like a.b.c.d
	destination.sin_port = htons(libraryData.serverPort);

		
	//bind
	localSock.sin_family = AF_INET;
	localSock.sin_addr.s_addr = htonl(INADDR_ANY);// orice adresa pt localSock
	localSock.sin_port = htons(libraryData.localPort); // portul sursa


	if (bind (sock, (struct sockaddr *) &localSock,sizeof(localSock)) )
	{
		perror("Nu s-a realizat bind\n");
		close(sock);
		return NULL;
	}
	
	length=sizeof(struct sockaddr_in);
	n=sendto(sock, pdSend, sizeof( *pdSend), 0, (struct sockaddr *) &destination, length);
	if (n < 0)
	{
		perror("Sendto");
		close(sock);
		return NULL;
	}
	
	//primim raspuns de la server o structura PacketData
	buf = (char *) malloc( sizeof(struct PacketData));
	pdRecv = (PacketData_t *) malloc( sizeof( *pdRecv) );
	if (!buf || !pdRecv)
	{
		perror("Eroare alocare buffer de primire.");
		close(sock);
		return NULL;
	}

	n = recvfrom(sock, buf, sizeof( *pdRecv), 0, NULL, NULL);
	if(n<0) 
		write(STDOUT_FILENO,"Nu s-au primit date!\n",21);
	else
	{
		memcpy(pdRecv, buf, n);
		printf("USER Date = %i, size = %i.\n", n, sizeof(*pdRecv));
		printf("USER Am primit: %i %i %i %i %i %i %i %i %i %i\n",
				pdRecv->myPort, pdRecv->myIP, pdRecv->myID, pdRecv->command,
				pdRecv->cmdArgs, pdRecv->responseCode, pdRecv->response[0],
				pdRecv->response[1], pdRecv->response[2], pdRecv->response[3]);
	}

		
	free(buf);
	close(sock);

    return pdRecv;
}





//OK
int NAT_register()
{
    PacketData_t *response;
    int retVal = 0;

    if (!libIsInitialized(libraryData))
    {
        writeToLog("NAT_register","Lib is not initialized...");
        return -1;
    }
    response = sendToServer(REGISTER, 0);
    if(!response)
        return -GENERAL_ERROR;
    if (response->responseCode != REGISTER_OK)
        retVal = 0 - response->responseCode;

    return retVal;
}

//OK
int NAT_requestCommunicationData(int clientID, CommunicationData_t *respDetails)
{
    PacketData_t *responsePD;
    int retVal = 0;

    if (!libIsInitialized(libraryData))
    {
        writeToLog("NAT_requestCommunicationData","Lib is not initialized...");
        return -1;
    }
    responsePD = sendToServer(REQUEST_DATA, clientID);
    if(!responsePD)
        return -GENERAL_ERROR;
    if (responsePD->responseCode != CLIENT_FOUND)
        retVal =  0 - responsePD->responseCode;
    else
    {
        respDetails->privatePort    = responsePD->response[0];//host byte order
        respDetails->privateIP      = responsePD->response[1];//host byte order
        respDetails->publicPort     = responsePD->response[2];//host byte order
        respDetails->publicIP       = responsePD->response[3];//host byte order
    }

    /*------*/
    printf(" NAT_requestCommunicationData.respDetails : privatePort = %i, privateIP = %x, \
                                publicPort = %i, publicIP = %x .\n",
                                respDetails->privatePort, respDetails->privateIP,
                                respDetails->publicPort, respDetails->publicIP);
    /*------*/

    return retVal;
}


//working..
int NAT_tryTraversal(CommunicationData_t *destDetail, RealEndPoints_t *endPoints)
{
    
    struct sockaddr_in publicDestination;
    struct sockaddr_in privateDestination;
    struct sockaddr_in localSock;
    int sock, length, publicToSend = 0, privateToSend = 0, i, x, rv, receivedResponse = 0;
    int porneste = 1;
    int retVal = -1;
    int n;
    fd_set mySocketFds;
    struct timeval tv;

    /*------*/
    printf("try trav.destDetails : privatePort = %i, privateIP = %x, \
                                publicPort = %i, publicIP = %x .\n",
                                destDetail->privatePort, destDetail->privateIP,
                                destDetail->publicPort, destDetail->publicIP);
    /*------*/


    //public endpoint
    publicDestination.sin_family = AF_INET;
	publicDestination.sin_addr.s_addr = destDetail->publicIP; //provided in network byte order.
    publicDestination.sin_port = htons(destDetail->publicPort);

    //private endpoint
    privateDestination.sin_family = AF_INET;
	privateDestination.sin_addr.s_addr = destDetail->privateIP; //provided in network byte order.
    privateDestination.sin_port = htons(destDetail->privatePort);

    //bind local socket
	localSock.sin_family = AF_INET;
	localSock.sin_addr.s_addr = htonl(INADDR_ANY);// orice adresa pt localSock
	localSock.sin_port = htons(libraryData.localPort); // portul sursa

    sock= socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0) perror("NAT_tryTraversal->Cannont create sending socket!");

	if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&porneste,sizeof(porneste)) < 0 )
		perror("NAT_tryTraversal->set sock opt SO_REUSEADDR");
    
    if (bind (sock, (struct sockaddr *) &localSock,sizeof(localSock)) )
	{
		perror("NAT_tryTraversal->bind error\n");
		close(sock);
		return -GENERAL_ERROR;
	}

    length=sizeof(struct sockaddr_in);
    for( i = 0; i < NAT_MAX_ATTEMPTS; i++)
    {
        publicToSend = NAT_MAGIC_NR | NAT_PUBLIC_FLAG;
        printf("+++++++++++size = %i \n",sizeof(publicToSend));
        n=sendto(sock, &publicToSend, sizeof(publicToSend), 0, (struct sockaddr *) &publicDestination, length);
	    if (n < 0)
	    {
		    perror("Send to public destination");
		    close(sock);
		    return -1;
	    }

        privateToSend = NAT_MAGIC_NR | NAT_PRIVATE_FLAG;
        n=sendto(sock, &privateToSend, sizeof(privateToSend), 0, (struct sockaddr *) &privateDestination, sizeof(privateDestination));
	    if (n < 0)
	    {
            printf("ERRNO = %i\n", errno);
		    perror("Send to private destination");
		    close(sock);
		    return -1;
	    }

        tv.tv_sec = NAT_TRAVERSAL_TIMEOUT;
        tv.tv_usec = 0;

        FD_ZERO(&mySocketFds);
        FD_SET(sock, &mySocketFds);
        x = sock + 1;

        rv = select(x, &mySocketFds, NULL, NULL, &tv);
        if (rv == -1)
            perror("select"); // error occurred in select()
        else if (rv == 0)
                printf("Timeout occurred!  No data after %i seconds.\n", NAT_TRAVERSAL_TIMEOUT);
            else
            {
                //the descriptor has data
                if (FD_ISSET(sock, &mySocketFds))
                {
                    n = recvfrom(sock, &receivedResponse, sizeof(receivedResponse), 0, NULL, NULL);
                    if(n<0)
                        write(STDOUT_FILENO,"NAT_TryTraversal: Nu s-au primit date!\n", 39);
                    else
	                {
		                printf("NAT_TryTraversal Date = %i, \n", n);
		                printf("NAT_TryTraversal Am primit: %i, %x\n", receivedResponse, receivedResponse);
                        if( isFromPrivate(receivedResponse) )
                        {
                            endPoints->IP = destDetail->privateIP;
                            endPoints->Port = destDetail->privatePort;
                            retVal = 0;
                            break;
                        }
                        if( isFromPublic(receivedResponse) )
                        {
                            endPoints->IP = destDetail->publicIP;
                            endPoints->Port = destDetail->publicPort;
                            retVal = 0;
                            break;
                        }
	                }
                }
            }
    }

    close(sock);
    return retVal;
}



//OK
int NAT_unregister()
{
    PacketData_t *response;
    int retVal = 0;

    if (!libIsInitialized(libraryData))
    {
        writeToLog("NAT_unregister","Lib is not initialized...");
        return -1;
    }
    response = sendToServer(UNREGISTER, 0);
    if(!response)
        return -GENERAL_ERROR;
    if (response->responseCode != UNREGISTER_OK)
        retVal = 0 - response->responseCode;

    return retVal;
}

int waitForConnection()
{
    struct sockaddr_in localSock;
    struct sockaddr_in fromaddr;
    int sock, porneste = 1, n, receivedInteger = 0;
    unsigned int fromaddrlen;

    if (!libIsInitialized(libraryData))
    {
        writeToLog("NAT_unregister","Lib is not initialized...");
        return -1;
    }

    //bind local socket
	localSock.sin_family = AF_INET;
	localSock.sin_addr.s_addr = htonl(INADDR_ANY);// orice adresa pt localSock
	localSock.sin_port = htons(libraryData.localPort); // portul sursa

    sock= socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0) perror("waitForConnection->Cannont create receiveing socket!");

	if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&porneste,sizeof(porneste)) < 0 )
		perror("waitForConnection->set sock opt SO_REUSEADDR");
    
    if (bind (sock, (struct sockaddr *) &localSock,sizeof(localSock)) )
	{
		perror("NAT_tryTraversal->bind error\n");
		close(sock);
		return -1;
	}

    n = recvfrom(sock, &receivedInteger, sizeof(receivedInteger), 0, (struct sockaddr *)&fromaddr, &fromaddrlen);
    if(n<0)
    {
        write(STDOUT_FILENO,"waitForConnection: Nu s-au primit date!\n", 40);
        return -1;
    }
    else
    {
        printf("waitForConnection Date = %i, de la ip = %X, port = %i\n", 
                 n, fromaddr.sin_addr.s_addr, ntohs(fromaddr.sin_port) );
        printf("waitForConnection Am primit: %i, %X\n", receivedInteger, receivedInteger);
        if( containsMagic(receivedInteger) )
        {
            printf("waitForConnection-> contine magic nr \n");
            //we've received a magic integer and we send it back
            n = sendto(sock, &receivedInteger, sizeof(receivedInteger), 0,
                (struct sockaddr *)&fromaddr, sizeof(fromaddr) );
            if (n < 0)
            {
                perror("waitForConnection sendto() failed");
                close(sock);
                return -1;
            }
        }
    }

    close(sock);
    return 0;
}



//se sterge dupa ce se face library
//pentru testare
int main()
{
    int port, ip, id, cmd, args, action;
    CommunicationData_t *otherClientData;
    RealEndPoints_t *endPoints;
    int retCode;
    
    while(1)
    {
        port = ip = id = cmd = args  = action = 0;
        printf("Introduceti: port id: ");
		scanf("%i %i", &port, &id);
        if (NAT_initLibrary(port, id, SERVER_PORT, 0x886da8c0) == 0)
        {
            printf("Introduceti: cmd: ");
		    scanf("%i", &cmd);
            switch(cmd)
            {
                case REGISTER:
                    printf("REGISTER ret code = %i\n", NAT_register());
                break;
                case UNREGISTER:
                    printf("UNREGISTER ret code = %i\n", NAT_unregister());
                break;
                case REQUEST_DATA:
                    printf("Introduceti: args: ");
		            scanf("%i", &args);
                    otherClientData = (CommunicationData_t *) malloc ( sizeof(* otherClientData));
                    if (!otherClientData)
                    {
                        printf("Unable to alloc memory\n");
                        continue;
                    }

                    retCode = NAT_requestCommunicationData(args, otherClientData);
                    printf("REQUEST_DATA ret code = %i\n", retCode);
                    if (retCode == 0)
                    {
                        printf("Comm details ID = %i: privatePort = %i, privateIP = %x, \
                                publicPort = %i, publicIP = %x .\n", args,
                                otherClientData->privatePort, otherClientData->privateIP,
                                otherClientData->publicPort, otherClientData->publicIP);
                    }
                    
                    //free(otherClientData);
                break;

                default:
                    printf("unknown command \n");
                break;
            }

            printf("Introduceti: action: ");
		    scanf("%i", &action);
            if ( action == 5 ) //try traversal
            {
                //try traversal
                endPoints = (RealEndPoints_t *) malloc (sizeof( *endPoints));
                if (!endPoints)
                {
                    printf(" Unable to alloc endPoints \n");
                }
                else
                    printf("Ret code try trav = %i\n", NAT_tryTraversal(otherClientData, endPoints));

            }
            if ( action == 6 )
            {
                //wait for incomming connection
                waitForConnection();
            }

            
            
        }
    }
    return 0;
}
